package com.lvy.hczn.front.business.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.lvy.hczn.front.business.common.CacheConstants;
import com.lvy.hczn.front.business.common.Constants;
import com.lvy.hczn.front.business.dto.SearchCondition;
import com.lvy.hczn.front.common.constant.RedisConstants;
import com.lvy.hczn.front.common.core.domain.AjaxResult;
import com.lvy.hczn.front.common.core.domain.BaseModel;
import com.lvy.hczn.front.common.core.domain.entity.SysDictData;
import com.lvy.hczn.front.common.exception.ServiceException;
import com.lvy.hczn.front.common.utils.DictUtils;
import com.lvy.hczn.front.system.domain.business.*;
import com.lvy.hczn.front.business.dto.MessageInfo;
import com.lvy.hczn.front.business.dto.Param;
import com.lvy.hczn.front.business.service.*;
import com.lvy.hczn.front.business.util.ResultUtil;
import com.lvy.hczn.front.common.core.redis.RedisCache;
import com.lvy.hczn.front.common.exception.UtilException;
import com.lvy.hczn.front.system.service.SpuService;
import com.lvy.hczn.front.system.service.WarehouseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 与WCS交互逻辑处理
 *
 * @author yw
 * @date 2022-02-22 10:42:10
 **/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class WcsServiceImpl implements WcsService {

    @Autowired
    private MqttService mqttService;

    //传入wcs的数值，如金额，重量的需乘以一定的比例，固定参数
    private final int num = 1;

    @Autowired
    private UpOrDownPolicyService upOrDownPolicyService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private GoodsBoxService goodsBoxService;

    @Autowired
    private GoodsBoxDetailService goodsBoxDetailService;

    @Autowired
    private PurchaseOrderDetailService purchaseOrderDetailService;

    @Autowired
    private WarehouseService warehouseService;

    @Autowired
    private OutboundOrderService outboundOrderService;

    @Autowired
    private OutboundOrderDetailService outboundOrderDetailService;

    @Autowired
    private SpuService spuService;

    @Autowired
    private MqttRecordService mqttRecordService;

    @Autowired
    private DeliveryOrderDetailService deliveryOrderDetailService;

    @Autowired
    private DeliveryOrderService deliveryOrderService;

    @Autowired
    private SaleOrderService saleOrderService;

    @Autowired
    private SaleOrderDetailService saleOrderDetailService;

    @Autowired
    private WarehousingEntryService warehousingEntryService;

    @Autowired
    private SocketIOServer socketIOServer;

    /**
     * 发送到mqtt
     *
     * @param param
     * @param topic
     * @param state
     * @return void
     * @author yw
     * @date 2022-03-08 15:30:14
     */
    private synchronized void toWcs(Param param, String topic, Integer state, UpOrDownPolicy upOrDownPolicy, String key) throws Exception {
        //todo 目前wcs做不到异步，每个发送到wcs指令,同一topic，等响应后再发第二个
        Boolean result = (Boolean) redisCache.get(key);
        if (result != null && result == false) {
            throw new UtilException("仓库忙碌中，请稍后重试");
        }
        redisCache.set(key, false);

        if (param.getMap() == null) {
            param.setMap(new WcsData());
        }
        WcsData data = param.getMap();
        if (param.getTempNum() != null) {
            data.setTempNum(param.getTempNum());
        }
        //空箱入库重量为空，赋值
        if (data.getTotalWeight() == null && param.getGoodsBox() != null) {
            data.setTotalWeight(param.getGoodsBox().getBoxStandardWeight());
        }
        /**
         * 发送到wcs
         */
        /**wcs需全结构体，若属性为空，则String填充""，Integer填充0**/
        complementStructure(data, upOrDownPolicy);
        data.setState(state);

        /**
         * 生成指令
         */
        upOrDownPolicy.setDataState(state);
        upOrDownPolicy.setStatus(Constants.UP_OR_DOWN_POLICE_STATUS_ZERO);
        //空箱批量出库
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            upOrDownPolicy.setBoxCode(IdUtil.getSnowflake().nextIdStr());
        }
        //异步查询有时查不到，放到Redis，WCS响应的时候，先处理数据，没有再查Redis
        String policyKey = com.lvy.hczn.front.business.common.CacheConstants.WCS_POLICY + upOrDownPolicy.getBoxCode();
        if (upOrDownPolicyService.save(upOrDownPolicy)) {
            String jsonStr = JSONUtil.toJsonStr(data);
            redisCache.set(policyKey, upOrDownPolicy, 2, TimeUnit.DAYS);
            mqttRecordService.initMqttRecord(upOrDownPolicy.getBoxCode(), topic, state, jsonStr);

            sendDelay();

            log.error("发送到wcs数据内容：{}", jsonStr);
            mqttService.sendToMqtt(topic, jsonStr);
        } else {
            log.error("指令存储异常：{}", JSON.toJSONString(upOrDownPolicy));
            throw new UtilException("指令存储异常");
        }
    }

    /**
     * 初始化货箱及货箱详情
     *
     * @param param
     * @return com.lvy.hczn.front.dto.Param
     * @author yw
     * @date 2022-04-01 22:35:26
     */
    public Param initGoodsBox(Param param, UpOrDownPolicy upOrDownPolicy, GoodsBox box) {
        GoodsBox goodsBox = param.getGoodsBox();
        if (StrUtil.isEmpty(goodsBox.getBoxCode())) {
            goodsBox.setBoxCode(param.getUpOrDownPolicy().getBoxCode());
        }
        //称重status为传过来，其他都为入库init
        if (StrUtil.isEmpty(goodsBox.getStatus())) {
            //离开缓存位响应，改为上架中
//            goodsBox.setStatus(MqConstants.GOODS_BOX_STATE_ZERO);
        }
        Warehouse warehouse = warehouseService.selectLocalWarehouseFront();
        if (warehouse != null) {
            goodsBox.setWarehouseId(warehouse.getId());
        }
        if (goodsBox.getWeight() == null) {
            goodsBox.setWeight(BigDecimal.ZERO);
        }
        if (goodsBox.getVolume() == null) {
            goodsBox.setVolume(BigDecimal.ZERO);
        }
        if (goodsBox.getBoxStandardWeight() == null && box != null) {
            goodsBox.setBoxStandardWeight(box.getBoxStandardWeight());
        }

        goodsBox.setWarehousingEntryTime(LocalDateTime.now());

        List<GoodsBoxDetail> list = param.getList();
        if (list != null && !list.isEmpty()) {
            /**同一货箱物品应相同温区或就近温区，只取第一个温区即可**/
            GoodsBoxDetail goodsBoxDetail = param.getList().get(0);
            Spu spu = spuService.getById(goodsBoxDetail.getSpuId());
            if (spu != null) {
                param.getMap().setTempNum(spu.getTemperatureZoneCode());
                goodsBox.setTemperatureZoneCode(spu.getTemperatureZoneCode());
            }
            if (param.getMap().getTotalWeight() == null) {
                param.getMap().setTotalWeight(BigDecimal.ZERO);
            }

            /**初始化货箱重量及称重重量**/
            if (Constants.STATE_PROCESS_TYPE_FOUR != upOrDownPolicy.getProcessType()) {
                //地平仓入库，初始化的货箱重量为，已入库的货箱商品重量，再累加入库重量
                if (param.getBigProduct() != null && param.getBigProduct().equals(com.lvy.hczn.front.common.constant.Constants.GOODS_COLLECT_YES)) {
                    if (box != null && box.getWeight() != null) {
                        goodsBox.setWeight(box.getWeight());
                    } else {
                        goodsBox.setWeight(BigDecimal.ZERO);
                    }
                } else {
                    goodsBox.setWeight(BigDecimal.ZERO);
                }

            } else {
                //强制入库，不改变货箱重量
                if (box != null && box.getWeight() != null) {
                    //采购入库的强制入库，仓库内的货箱为空,取入库货箱列表数据
                    if (box.getDetailList() == null || box.getDetailList().isEmpty()) {
//                        BigDecimal result = list.stream().map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
//                        goodsBox.setWeight(result);
                        param.getMap().setTotalWeight(goodsBox.getWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)));
                    } else {
                        param.getMap().setTotalWeight(box.getWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)));
                        goodsBox.setWeight(box.getWeight());
                    }
                } else {
                    param.getMap().setTotalWeight(goodsBox.getWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)));
                }

            }
            list.forEach(item -> {
                item.setBoxCode(param.getGoodsBox().getBoxCode());
                if (item.getWeight() != null) {
                    if (item.getWeight().compareTo(BigDecimal.ZERO) > 0) {
                        if (item.getWeightOfPerBox() == null || item.getWeightOfPerBox().equals(BigDecimal.ZERO)) {
                            if (item.getQuantity() != null && item.getQuantity() > 0) {
                                item.setWeightOfPerBox(item.getWeight().divide(BigDecimal.valueOf(item.getQuantity()), 2, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP));
                            }
                        }
                        if (Constants.STATE_PROCESS_TYPE_FOUR != upOrDownPolicy.getProcessType()) {
                            //非强制入库。取货箱物品总重量
                            param.getMap().setTotalWeight(param.getMap().getTotalWeight().add(item.getWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT))));
                            goodsBox.setWeight(goodsBox.getWeight().add(item.getWeight()));
                        }
                        if (item.getVolume() != null) {
                            goodsBox.setVolume(goodsBox.getVolume().add(item.getVolume()));
                        }
                    } else {
                        item.setWeight(BigDecimal.ZERO);
                        item.setVolume(BigDecimal.ZERO);
                    }
                }
            });
//            goodsBoxDetailService.saveOrUpdateBatch(param.getList());
        }
        goodsBox.setType(Integer.parseInt(upOrDownPolicy.getBoxCode().substring(2, 3)));
        param.setGoodsBox(goodsBox);
        return param;
    }

    /**
     * 组装结构体，wcs要求结构体必须完整
     * 如：为空的String补""或Integer补:0,数组补50长度，不够，则0填充
     *
     * @param data
     * @return void
     * @author yw
     * @date 2022-03-24 15:57:13
     */
    public void complementStructure(WcsData data, UpOrDownPolicy upOrDownPolicy) {
        /*if (data.getIdNum() == null) {
            data.setIdNum(0);
        }*/
        if (data.getTempNum() == null) {
            data.setTempNum(0);
        }
        if (StrUtil.isEmpty(data.getTakeNum())) {
            data.setTakeNum("");
        }
        if (data.getSeatID() == null) {
            data.setSeatID(0);
        }
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            //空箱出库，无货箱编号，默认写""到wcs
            data.setBoxCode("");
        } else {
            data.setBoxCode(upOrDownPolicy.getBoxCode());
        }

        /*if (upOrDownPolicy.getTotalWeight() == null) {
            data.setTotalWeight(MqConstants.GOODS_BOX_STANDARD_WEIGHT);
        }*/

        //正负公差值
        if (data.getPartErrorValue() == null || data.getPartErrorValue().size() == 0) {
            data.setPartErrorValue(new ArrayList<>());
            GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
            if (goodsBox != null) {
                if (goodsBox.getErrorMax() == null) {
                    goodsBox.setErrorMax(Constants.GOODS_BOX_ERROR_MAX);
                }
                if (goodsBox.getErrorMin() == null) {
                    goodsBox.setErrorMin(Constants.GOODS_BOX_ERROR_MIN);
                }
                data.getPartErrorValue().add(goodsBox.getErrorMax().intValue());
                data.getPartErrorValue().add(goodsBox.getErrorMin().intValue());
            } else {
                data.getPartErrorValue().add(Constants.GOODS_BOX_ERROR_MAX.intValue());
                data.getPartErrorValue().add(Constants.GOODS_BOX_ERROR_MIN.intValue());
            }
        }
    }

    /**
     * 发送消息到wcs
     *
     * @param param
     * @param upOrDownPolicy
     * @return
     * @author yw
     * @date 2022-03-20 15:12:58
     */
    @Override
    public void sendMessage(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        //空箱下架，可以不指定货箱编号批量下架，其他情况都校验货箱编号
        if (param.getOutbound() != null && StrUtil.isEmpty(param.getGoodsBox().getBoxCode()) && Constants.OUTBOUND_TYPE_ONE == param.getOutbound()) {

        } /*else if (!upOrDownPolicy.getBoxCode().startsWith(Constants.PREFIX_BOX) || upOrDownPolicy.getBoxCode().length() != Constants.GOODS_BOX_LENGTH) {
            throw new UtilException("货箱编号不正确");
        }*/ else {
            String reg = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.SYS_DICT_BOX_CODE_REG, com.lvy.hczn.front.common.constant.Constants.SYS_DICT_BOX_CODE_REG_INFO);

            //正则表达式校验：前两位为大写字母，第三位为0或1，剩余九位为0-9的数字
            if (!upOrDownPolicy.getBoxCode().matches(reg)) {
                throw new UtilException("货箱编号" + upOrDownPolicy.getBoxCode() + "格式错误，请检查");
            }
        }

        boolean state = Constants.checkProcessState(upOrDownPolicy.getProcessState());
        if (!state) {
            throw new UtilException("流程不存在");
        }
        //更详细校验
        if (upOrDownPolicy == null || upOrDownPolicy.getProcessState() == null) {
            throw new UtilException("状态为空");
        }
        if (upOrDownPolicy.getProcessType() == null) {
            throw new UtilException("流程类型为空");
        }

        //空箱出库以及货箱称重，不指定货箱编号，无需校验，其他流程新校验
        if (Constants.STATE_PROCESS_TYPE_ZERO != upOrDownPolicy.getProcessType() && !upOrDownPolicy.getProcessType().equals(Constants.STATE_PROCESS_TYPE_FIVE)) {
            //称重和调度后，才会有记录，未经过这两步，则认为是新货箱
            GoodsBox box = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
            if (box == null) {
                throw new UtilException("新货箱必须称重再入库");
            }
        }

        //指令类型和货箱编号确定唯一
        UpOrDownPolicy tmp = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, upOrDownPolicy.getBoxCode()).eq(UpOrDownPolicy::getType, upOrDownPolicy.getType()));
        /*if (tmp != null) {
            throw new UtilException(tmp.getBoxCode() + "货箱已处于流程状态中" + tmp.getProcessState());
        }*/
        if (tmp != null) {
            upOrDownPolicyService.delPhysicalById(tmp.getId());
        }
        Boolean signal = (Boolean) redisCache.get(CacheConstants.REDIS_KEY_WCS_SIGNAL);
        if (signal == null) {
            throw new UtilException("已与wcs断开连接");
        }
        switch (upOrDownPolicy.getProcessType()) {
            case Constants.STATE_PROCESS_TYPE_ZERO:
                //出库
                outbound(param, upOrDownPolicy);
                break;
            case Constants.STATE_PROCESS_TYPE_ONE:
                //分拣、生产
                sorting(param, upOrDownPolicy);
                break;
            case Constants.STATE_PROCESS_TYPE_TWO:
            case Constants.STATE_PROCESS_TYPE_SEVEN:
                //分拣称重
                sortingWeight(param, upOrDownPolicy);
                break;
            case Constants.STATE_PROCESS_TYPE_THREE:
                //入库
                entryByCondition(param, upOrDownPolicy);
                break;
            case Constants.STATE_PROCESS_TYPE_FOUR:
                //强制入库
                entryForce(param, upOrDownPolicy);
                break;
            case Constants.STATE_PROCESS_TYPE_FIVE:
                //货箱毛重称重
                netWeight(param, upOrDownPolicy);
                break;
        }
    }

    /**
     * 出库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-02 18:57:00
     */
    public void outbound(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        /**
         * outbound:货箱出库不入库,1、空箱；2、非空箱出库且不入库；
         */
        switch (param.getOutbound()) {
            case 1:
                //出库空箱
                //bit8+bit2+bit3+温区,温区可不写
                int empty = (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_EIGHT);
                if (param.isOutboundAndEntry()) {
                    //出库空箱到缓存位（不指定是否继续入库，即可能会入库）
                    empty += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_THREE);
                }
                empty += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_TWO);
//                Thread.sleep(MqConstants.TOPIC_MQTT_SEND_DELAY);
                toWcs(param, Constants.TOPIC_MQTT_WMS_TO_WCS, empty, upOrDownPolicy, CacheConstants.WCS_RESPONSE + "wms");
                break;
            case 2:
                //出库：非空箱出库且不入库（提前告知箱子不入库，箱子到位直接可以拿走）
                int stateFour = (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_EIGHT);
                if (param.isOutboundAndEntry()) {
                    //出库：非空箱出库（不提前指定是否不入库，入客户取货，原料分拣，由操作人员触发箱子出库还是入库），只会下一个箱子
                    //如：分拣只有一个一个出，则需带bit3，
                    stateFour += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_THREE);
                }
                stateFour += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_ONE);
//                Thread.sleep(MqConstants.TOPIC_MQTT_SEND_DELAY);
                toWcs(param, Constants.TOPIC_MQTT_WMS_TO_WCS, stateFour, upOrDownPolicy, CacheConstants.WCS_RESPONSE + "wms");
                break;
        }
    }

    /**
     * 分拣、生产
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-02 18:57:54
     */
    public void sorting(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        int stateOne = (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_TEN);
        stateOne += Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_ONE);
        //提前告诉WCS此箱可能会继续入库的货箱，（适用于所有，即，不带bit3，会持续下架，带bit3则只下一个箱子）
        stateOne += Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_THREE);
//        Thread.sleep(MqConstants.TOPIC_MQTT_SEND_DELAY);
        toWcs(param, Constants.TOPIC_MQTT_WMS_TO_WCS, stateOne, upOrDownPolicy, CacheConstants.WCS_RESPONSE + "wms");
    }

    /**
     * 分拣中称重
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-05-29 09:41:04
     */
    public void sortingWeight(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        String key = CacheConstants.WCS_RESPONSE + "weight";
        Boolean result = (Boolean) redisCache.get(key);
        if (result != null && result == false) {
            throw new UtilException("分拣称重，仓库忙碌中，请稍后重试");
        }
        redisCache.set(key, false);
        WcsData data = new WcsData();
        data.setBoxCode(upOrDownPolicy.getBoxCode());
        //货箱剩余重量
        data.setTotalWeight(upOrDownPolicy.getTotalWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)));

        //加上货箱重量
        GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
        if (goodsBox != null) {
            data.setTotalWeight(data.getTotalWeight().add(goodsBox.getBoxStandardWeight()));
        }

        /**
         * 生成指令
         */
        if (param.getMap() == null) {
            param.setMap(data);
        }
        //日志记录看到保存到数据库时间比wcs响应wms时间慢，即先响应的时间在保存到数据库的时间前面，响应那边会查不到数据，放到redis试试
        redisCache.set(upOrDownPolicy.getType() + ":" + upOrDownPolicy.getBoxCode(), upOrDownPolicy, 3, TimeUnit.MINUTES);
        upOrDownPolicyService.save(upOrDownPolicy);
//        sendMessage(param, upOrDownPolicy);
        log.error("分拣中称重，传入wcs参数：{}", JSONUtil.toJsonStr(data));
        mqttService.sendToMqtt(Constants.TOPIC_MQTT_MQ3_WEIGHT_WMS, JSONUtil.toJsonStr(data));
    }

    /**
     * 判断入库空箱还是非空箱
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-02 17:28:33
     */
    public void entryByCondition(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
//        param.getGoodsBox().setStatus(MqConstants.GOODS_BOX_STATE_ZERO);
        if (param.getList() == null || param.getList().isEmpty()) {
            param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
            //空箱入库
            //入库：入库空箱：Bit9 & Bit2
            int empty = (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_NINE);
            empty += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_TWO);
            if (param.isOutboundAndEntry()) {
                //出库空箱到缓存位（不指定是否继续入库，即可能会入库）,然后重新发送入库指令，则加bit3,如：分拣的箱子
                empty += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_THREE);
            }
//            Thread.sleep(MqConstants.TOPIC_MQTT_SEND_DELAY);
            toWcs(param, Constants.TOPIC_MQTT_WMS_TO_WCS, empty, upOrDownPolicy, CacheConstants.WCS_RESPONSE + "wms");
        } else {
            //非空箱入库
            //WMS反馈货箱是否继续入库
            //入库：非空箱入库入：订单箱，原材料入库等：Bit9 & Bit1
            int notEmpty = (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_NINE);
            notEmpty += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_ONE);
            if (param.isOutboundAndEntry()) {
                //出库空箱到缓存位（不指定是否继续入库，即可能会入库）,然后重新发送入库指令，则加bit3,如：分拣的箱子
                notEmpty += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_THREE);
            }
//            Thread.sleep(MqConstants.TOPIC_MQTT_SEND_DELAY);
            toWcs(param, Constants.TOPIC_MQTT_WMS_TO_WCS, notEmpty, upOrDownPolicy, CacheConstants.WCS_RESPONSE + "wms");
        }
    }

    /**
     * 强制入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-02 19:08:17
     */
    public void entryForce(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {

        List<GoodsBoxDetail> list = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, upOrDownPolicy.getBoxCode()));
        //采购入库时，强制入库的货箱为空箱，则取传过来的list校验，告诉wcs入库的是原料箱还是空箱
        if (list == null || list.isEmpty()) {
            list = param.getList();
        }
        BigDecimal result = list.stream().map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
        //强制入库
        //Bit4&Bit9为强制入库标识
        int stateNine = (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_FOUR);
        if (result.compareTo(BigDecimal.ZERO) > 0) {
            //非空箱
            stateNine += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_ONE);
        } else {
            goodsBoxDetailService.delPhysicalByBoxCode(upOrDownPolicy.getBoxCode());
            //空箱
            stateNine += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_TWO);
        }
        stateNine += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_NINE);
        if (param.isOutboundAndEntry()) {
            //出库空箱到缓存位（不指定是否继续入库，即可能会入库）,然后重新发送入库指令，则加bit3,如：分拣的箱子
            stateNine += (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_THREE);
        }
//        Thread.sleep(MqConstants.TOPIC_MQTT_SEND_DELAY);
        toWcs(param, Constants.TOPIC_MQTT_WMS_TO_WCS, stateNine, upOrDownPolicy, CacheConstants.WCS_RESPONSE + "wms");
    }

    /**
     * 分拣称重
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-02 19:02:31
     */
    public synchronized void weight(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        //称重，分拣可单个称重和批量称重
//        Thread.sleep(MqConstants.TOPIC_MQTT_SEND_DELAY);
        toWcs(param, Constants.TOPIC_MQTT_MQ3_WEIGHT_WMS, null, upOrDownPolicy, CacheConstants.WCS_RESPONSE + "weight");
    }


    /**
     * 净重
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-02 19:02:17
     */
    public void netWeight(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        //新的货箱毛重称重
        int twentyEight = (int) Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_FIVE);
        twentyEight += Math.pow(Constants.STATE_TO_WCS_BASE_NUMBER, Constants.STATE_TO_WCS_NINE);
//        twentyEight += Math.pow(MqConstants.STATE_TO_WCS_BASE_NUMBER, MqConstants.STATE_TO_WCS_ONE);
//        Thread.sleep(MqConstants.TOPIC_MQTT_SEND_DELAY);
        toWcs(param, Constants.TOPIC_MQTT_WMS_TO_WCS, twentyEight, upOrDownPolicy, CacheConstants.WCS_RESPONSE + "wms");
    }

    /**
     * 数据转换,数据如重量，金额等转换到wcs,乘以一定的num数值（1000）,转换为wms除以一定的数值(1000)
     *
     * @param data
     * @return com.lvy.hczn.model.front.WcsData
     * @author yw
     * @date 2022-03-21 11:13:53
     */
    @Override
    public WcsData convertData(WcsData data, boolean toWcs, int num) {
        if (toWcs) {
            if (data.getTotalWeight() != null) {
                data.setTotalWeight(data.getTotalWeight().multiply(BigDecimal.valueOf(num)));
            }
        } else {
            if (data.getTotalWeight() != null) {
                data.setTotalWeight(data.getTotalWeight().divide(BigDecimal.valueOf(num)));
            }
        }
        return data;
    }

    /**
     * 根据出库单取货出库
     *
     * @param code
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-03-22 21:04:58
     */
  /*  @Override
    public void outboundOrderByCode(String code, UpOrDownPolicy upOrDownPolicy) throws InterruptedException {
        if (StrUtil.isEmpty(code)) {
            throw new UtilException("出库单为空");
        }
        if (upOrDownPolicy == null) {
            throw new UtilException("指令信息为空");
        }
        Boolean signal = (Boolean) redisCache.get(MqConstants.REDIS_KEY_WCS_SIGNAL);
        if (signal == null) {
            throw new UtilException("已与wcs断开连接");
        }
        //todo 根据出库单编号查询出库单详情
        List<OutboundOrderDetail> list = new ArrayList();
        OutboundOrderDetail outboundOrderDetail = new OutboundOrderDetail();
        list.add(outboundOrderDetail);
        for (OutboundOrderDetail item : list) {
            WcsData wcsData = new WcsData();
            if (StrUtil.isEmpty(item.getGoodsBoxCode())) {
                //测试
                wcsData.setBoxCode("LYG123456789");
            } else {
                wcsData.setBoxCode(item.getGoodsBoxCode());
            }
            upOrDownPolicy.setProcessState(MqConstants.STATE_TO_WCS_ONE);
            upOrDownPolicy.setBoxCode(wcsData.getBoxCode());
            upOrDownPolicy.setType(MqConstants.BOX_OPERATION_DOWN);
            wcsData.setTakeNum(code);
            Param param = new Param();
            param.setMap(JSONUtil.toBean(JSONUtil.toJsonStr(wcsData), Map.class));
            this.sendMessage(param, upOrDownPolicy);
        }
    }*/

    /**
     * 入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-03-23 13:50:45
     */
    @Transactional
    public void entry(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {

        if (StrUtil.isEmpty(upOrDownPolicy.getType())) {
            upOrDownPolicy.setType(Constants.BOX_OPERATION_UP);
        }
        /**货箱及货箱详情处理**/
        if (param.getMap() == null) {
            param.setMap(new WcsData());
        }
        GoodsBox box = goodsBoxService.selectIncDetailByBoxCode(upOrDownPolicy.getBoxCode());

        initGoodsBox(param, upOrDownPolicy, box);
        //货箱入库，存在记录有两种情况，1、新货箱称重，2、仓库调度(货箱状态以已下架生成到货箱表，新增一条货箱调度记录)
        if (box == null) {
            //当流程为非货箱称重，则校验视为新货箱，需称重再入库留存
            if (!upOrDownPolicy.getProcessType().equals(Constants.STATE_PROCESS_TYPE_FIVE)) {
                throw new UtilException("新货箱必须称重再入库");
            }
            //新箱
//            param.getGoodsBox().setWarehousingEntryTime(LocalDateTime.now());
            if (param.getMap().getTotalWeight() == null) {
                List<SysDictData> list = DictUtils.getDictCache(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_INFO);
                if (list == null || list.isEmpty()) {
                    throw new UtilException("箱型尺寸信息为空");
                }
                for (SysDictData sysDictData : list) {
                    if (sysDictData.getDictLabel().equals(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_BIG_INFO)) {
                        param.getMap().setTotalWeight(new BigDecimal(sysDictData.getRemark()));
                        param.getGoodsBox().setBoxStandardWeight(new BigDecimal(sysDictData.getRemark()));
                        break;
                    }
                }
                String boxErrMax = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.BOX_TYPE_ERROR_RANGE, com.lvy.hczn.front.common.constant.Constants.BOX_ERROR_MAX_BIG);
                String boxErrMin = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.BOX_TYPE_ERROR_RANGE, com.lvy.hczn.front.common.constant.Constants.BOX_ERROR_MIN_BIG);
                param.getGoodsBox().setErrorMin(new BigDecimal(boxErrMin));
                param.getGoodsBox().setErrorMax(new BigDecimal(boxErrMax));
            }
            Warehouse warehouse = warehouseService.selectLocalWarehouseFront();
            if (warehouse != null) {
                param.getGoodsBox().setWarehouseId(warehouse.getId());
            }
//            goodsBoxService.save(param.getGoodsBox());
            redisCache.set(CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getProcessType(), param, 3, TimeUnit.DAYS);
        } else {
            if (!Constants.GOODS_BOX_STATE_TWO.equals(box.getStatus()) && !Constants.GOODS_BOX_STATE_FIVE.equals(box.getStatus()) && !Constants.GOODS_BOX_STATE_SIX.equals(box.getStatus())) {
//                throw new UtilException("货箱状态不符");
            } else {
                if (param.getList() == null || param.getList().isEmpty()) {
                    param.getMap().setTotalWeight(box.getBoxStandardWeight());
                    //设置温区
                /*if (!StrUtil.isEmpty(box.getTemperatureZoneCode())) {
                    param.getMap().setTempNum(Integer.valueOf(box.getTemperatureZoneCode()));
                }*/
                } else {
                    //设置入库重量：采购入库物品重量,加上货箱标准重量
                    param.getMap().setTotalWeight(param.getMap().getTotalWeight().add(box.getBoxStandardWeight()));
                }

                if (StrUtil.isEmpty(param.getGoodsBox().getStatus())) {
                    param.getGoodsBox().setStatus(box.getStatus());
                }

                param.getGoodsBox().setId(box.getId());
                //todo 最好在响应成功后改
//                goodsBoxService.updateById(param.getGoodsBox());
                redisCache.set(CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getProcessType(), param, 3, TimeUnit.DAYS);
            }
        }

        //区分大件商品退货和非大件商品，入的仓库和流程不一样
        //忘了，应该直接用node-red模拟WCS响应的
        if (param.getBigProduct() != null && param.getBigProduct().equals(com.lvy.hczn.front.common.constant.Constants.GOODS_COLLECT_YES)) {
            if (StrUtil.isEmpty(param.getGoodsBox().getId()) && box != null) {
                //货箱已采购入库，不会走上面的逻辑赋值，需重新赋值
                param.getGoodsBox().setId(box.getId());
            }
            entryGoodsCollect(param, upOrDownPolicy);
        } else {
            //入库
            this.sendMessage(param, upOrDownPolicy);
        }
    }

    /**
     * @Description 地平仓入库
     * @Param
     * @Author yw
     * @Date 2024/6/21 13:55
     * @Return
     **/
    public void entryGoodsCollect(Param param, UpOrDownPolicy upOrDownPolicy) {
        //TODO 业务处理，到时考虑是否需要在这区分还是在上游区分入库，比如采购入库，退货入库
        param.getGoodsBox().setStatus(Constants.GOODS_BOX_STATE_THREE);
        param.getGoodsBox().setWarehouseRow(0);
        param.getGoodsBox().setWarehouseNumber(0);
        param.getGoodsBox().setWarehouseColumn(0);
        param.getGoodsBox().setActTemperatureCode(param.getGoodsBox().getActTemperatureCode());
        redisCache.set(CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getProcessType(), param, 3, TimeUnit.DAYS);
    }

    /**
     * 分拣原料入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-07 21:22:53
     */
    @Override
    public synchronized void sortingEntry(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(param.getUpOrDownPolicy().getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_NINE);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_THREE);
        upOrDownPolicy.setType(Constants.BOX_OPERATION_UP);
        param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ONE);
        GoodsBox goodsBox = goodsBoxService.selectIncDetailByBoxCode(upOrDownPolicy.getBoxCode());
        if (goodsBox != null && !goodsBox.getDetailList().isEmpty()) {
            param.setList(goodsBox.getDetailList());
            param.getGoodsBox().setWeight(goodsBox.getWeight());
        }
        this.entry(param, upOrDownPolicy);
    }

    /**
     * 合箱原料入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-07 21:22:53
     */
    @Override
    public synchronized void mergeEntry(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(param.getUpOrDownPolicy().getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
       /* if (param.getList() == null || param.getList().isEmpty()) {
            throw new UtilException("合箱详情为空");
        }*/
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_NINE);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_THREE);
        upOrDownPolicy.setType(Constants.BOX_OPERATION_UP);

        List<GoodsBoxDetail> list = param.getList();
        //都报损后，货箱为空时，置为空箱
        if (list == null || list.isEmpty()) {
            param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
            upOrDownPolicy.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
            log.error("合箱原料箱变空箱1：｛｝", JSON.toJSONString(param));
            //原料箱变空箱后，库位调整，释放库位
            warehousingEntryService.boxShelfLocationMsgSingle(param.getGoodsBox(), true);
        } else {
            BigDecimal totalWeight = list.stream().map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (totalWeight.compareTo(BigDecimal.ZERO) > 0) {
                param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ONE);
                upOrDownPolicy.setBoxType(Constants.GOODS_BOX_TYPE_ONE);
            } else {
                param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
                upOrDownPolicy.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
                log.error("合箱原料箱变空箱2：｛｝", JSON.toJSONString(param));
                //原料箱变空箱后，库位调整，释放库位
                warehousingEntryService.boxShelfLocationMsgSingle(param.getGoodsBox(), true);
                param.setList(null);
            }
        }
        this.entry(param, upOrDownPolicy);
    }

    /**
     * 请求分拣出库
     *
     * @param param
     * @return void
     * @author yw
     * @date 2022-05-11 20:28:53
     */
    @Override
    public synchronized void sortingOut(Param param) throws Exception {
        if (StrUtil.isEmpty(param.getUpOrDownPolicy().getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
        //校验是否是已上架的原料箱
        GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, param.getUpOrDownPolicy().getBoxCode()).eq(GoodsBox::getStatus, Constants.GOODS_BOX_STATE_THREE).eq(GoodsBox::getBoxType, Constants.GOODS_BOX_TYPE_ONE));
        if (goodsBox != null) {
            param.getUpOrDownPolicy().setProcessState(Constants.STATE_TO_WCS_ONE);
            param.getUpOrDownPolicy().setType(Constants.BOX_OPERATION_DOWN);
            param.getUpOrDownPolicy().setProcessType(Constants.STATE_PROCESS_TYPE_ONE);
            if (param.getMap() == null) {
                param.setMap(new WcsData());
            }
            if (StrUtil.isEmpty(param.getUpOrDownPolicy().getTakeNum())) {
                param.getMap().setTakeNum(IdUtil.getSnowflake().nextIdStr());
            } else {
                param.getMap().setTakeNum(param.getUpOrDownPolicy().getTakeNum());
            }

            /*LambdaUpdateWrapper<GoodsBox> goodsBoxWrapper = new LambdaUpdateWrapper<>();
            goodsBoxWrapper.eq(GoodsBox::getBoxCode, goodsBox.getBoxCode()).set(GoodsBox::getStatus, MqConstants.GOODS_BOX_STATE_ONE);
            goodsBoxService.update(goodsBoxWrapper);*/
            this.sendMessage(param, param.getUpOrDownPolicy());
        } else {
            //如，在分拣过程中，小货箱叠加的时候，会存在挪库情况，挪动此货箱时，无法下架，需抛出异常，重新放回下架队列
            log.error("货箱状态或类型异常:{}", JSON.toJSONString(param));
            /*挪库情况下，WMS没有指令，则抛出异常，重新放入队列**/
            UpOrDownPolicy upOrDownPolicyInfo = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, param.getUpOrDownPolicy().getBoxCode()).eq(UpOrDownPolicy::getType, Constants.BOX_OPERATION_DOWN).last("limit 1"));
            if (upOrDownPolicyInfo == null) {
                GoodsBox goodsBox1 = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, param.getUpOrDownPolicy().getBoxCode()));
                //是空箱则过滤，继续下架下一个，懒得排查是放相同货箱编号到队列还是分拣完成更改变成空箱了，先这样处理，到时再看
                if (goodsBox1 != null && !Constants.GOODS_BOX_TYPE_ZERO.equals(goodsBox1.getBoxType())) {
                    throw new UtilException("货箱状态或类型异常：" + JSON.toJSONString(param));
                } else {
                    //继续下架下一个
                    sortingOutBatch(param, false);
                }
            } else {
                //继续下架下一个
                sortingOutBatch(param, false);
            }
        }
    }

    /**
     * 连续出库（同一topic，wcs响应一次，再发下一个指令，wcs目前做不到一次性发，总仓会是一次性发，前置仓连续发，应该可以满足需求，暂时不改）
     * <p>
     * 没有需出库货箱时，检查一下redis有没待分拣出库货箱，有则出库
     * wcs消息队列存在数量限制，第一次进入分拣页面，响应的时候，会批量出库一波，会存在队列满了，或者同一topic抢不到的情况(wcs目前未达到一次性发只能连续发，同一topic等wcs响应了，再发下一个)
     * 响应未抢到，则放回redis，等货箱离开库位响应的时候，wcs释放队列，再发出库指令，也抢不到，目前只能刷新页面（离开缓存位会刷新，以及手动刷新分拣页面）
     * 暂时不考虑定时任务发送指令
     *
     * @param param
     * @return void
     * @author yw
     * @date 2022-07-18 16:05:09
     */
    @Override
    public synchronized void sortingOutBatch(Param param, boolean leaveInBuffer) throws Exception {
        if (param.getUpOrDownPolicy() != null && !StrUtil.isEmpty(param.getUpOrDownPolicy().getClientId())) {
            String clientId = param.getUpOrDownPolicy().getClientId();
            if (StrUtil.isEmpty(clientId)) {
                throw new UtilException("客户端信息为空");
            }
            List<Object> objects = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD + clientId, 0, -1);
            log.error("分拣下架原料货箱编号列表：" + JSONUtil.toJsonStr(objects));
            Object o = redisCache.delListLeftPop(CacheConstants.SORT_TASK_DOWNLOAD + clientId, 3, TimeUnit.SECONDS);
            log.error("弹出的元素：{}", JSON.toJSONString(o));
            if (o == null) {
                /***当前仓库下架货箱为空时，查询其他仓库下架*/
                JSONArray array = (JSONArray) redisCache.get(CacheConstants.DICT_TYPE_WH_COM);
                if (array != null && !array.isEmpty()) {
                    for (int i = 0; i < array.size(); i++) {
                        SysDictData data = (SysDictData) array.get(i);
                        Long size = redisCache.getListSize(CacheConstants.SORT_TASK_DOWNLOAD + data.getDictValue());
                        if (size != null && size > 0) {
                            o = redisCache.delListLeftPop(CacheConstants.SORT_TASK_DOWNLOAD + data.getDictValue(), 3, TimeUnit.SECONDS);
                            log.error("弹出的元素1：{}", JSON.toJSONString(o));
                            clientId = data.getDictValue();
                            break;
                        }
                    }
                }
            }
            if (o != null) {
                String boxCode = String.valueOf(o);
                //离开缓存位时，校验在途货箱以及到位货箱，货箱内是否有相同spu，有则暂不出库，不然若是在不同仓库，则会出现，待出库的比正在出库的先到位，则会出现先分拣物品多的货箱
                //以及发到wcs的货箱
                boolean flag = false;
                /*if (leaveInBuffer) {
                 *//**待出库列表的货箱详情与在途货箱中的货箱详情对比，自在待出库列表中，有一个spu时存在在在途货箱中，则待出库列表的货箱不能出库**//*
                    List<GoodsBoxDetail> list = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, boxCode).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
                    List<GoodsBoxDetail> downloadingList = goodsBoxDetailService.selectByUpOrDownPolicyType(MqConstants.BOX_OPERATION_DOWN, MqConstants.STATE_PROCESS_TYPE_ONE);
                    if (downloadingList != null && !downloadingList.isEmpty()) {
                        log.error("sortingOutBatch正在出库：{}", JSONUtil.toJsonStr(downloadingList));
                        LinkedHashMap<String, List<GoodsBoxDetail>> map = downloadingList.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getSpuId, LinkedHashMap::new, Collectors.toList()));
                        if (!map.isEmpty()) {
                            for (GoodsBoxDetail goodsBoxDetailInfo : list) {
                                if (map.containsKey(goodsBoxDetailInfo.getSpuId())) {
                                    log.error("已存在,{}", JSONUtil.toJsonStr(goodsBoxDetailInfo));
                                    flag = true;
                                    break;
                                }
                            }
                        }
                    }
                }*/
                log.error("flag:{};boxCode:{}", flag, boxCode);
                if (!flag) {
                    Param paramInfo = new Param();
                    UpOrDownPolicy upOrDownPolicy = new UpOrDownPolicy();
                    upOrDownPolicy.setBoxCode(boxCode);
                    upOrDownPolicy.setClientId(clientId);
                    upOrDownPolicy.setConsoleId("0");
                    upOrDownPolicy.setBoxType(Constants.GOODS_BOX_TYPE_ONE);
                    paramInfo.setUpOrDownPolicy(upOrDownPolicy);
                    //分拣发指令，所有指令作为一个批次，wcs按批次内顺序执行
                    Object takeNum = redisCache.get(CacheConstants.SORT_TASK_TAKE_NUM + clientId);
                    if (takeNum != null) {
                        paramInfo.getUpOrDownPolicy().setTakeNum(String.valueOf(takeNum));
                    }

                    try {
                        this.sortingOut(paramInfo);
                    } catch (Exception e) {

                        checkRedisExist(upOrDownPolicy);

                        log.error("sortingOutBatch，发送分拣货箱失败:{},{}", e, JSONUtil.toJsonStr(upOrDownPolicy));
                        throw new UtilException("发送分拣货箱失败，请稍后重试！,{}", e);
                    }
                } else {
                    //正在出库的货箱，存在相同spu，则放回redis，暂不出库
                    log.error("重新存入redis,{}", boxCode);
                    redisCache.setListOfLeft(CacheConstants.SORT_TASK_DOWNLOAD + clientId, boxCode);
                }
            }
        }
    }

    /**
     * @Description 查询一遍有没有，没有在set
     * @Param [upOrDownPolicy]
     * @Author yw
     * @Date 2024/6/1 11:13
     * @Return void
     **/
    @Override
    public void checkRedisExist(UpOrDownPolicy upOrDownPolicy) {
        /*查询一遍有没有，没有在set**/
        Long num = redisCache.getListSize(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId());
        if (num != null && num > 0) {
            boolean exist = false;
            for (int i = 0; i < num; i++) {
                Object object = redisCache.getListIndex(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId(), i);
                if (String.valueOf(object).equals(upOrDownPolicy.getBoxCode())) {
                    exist = true;
                    break;
                }
            }
            if (!exist) {
                redisCache.setListOfLeft(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId(), upOrDownPolicy.getBoxCode());
            }
        } else {
            redisCache.setListOfLeft(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId(), upOrDownPolicy.getBoxCode());
        }
    }

    /**
     * 分拣订单入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return
     * @author yw
     * @date 2022-04-19 22:48:23
     */
    @Override
    public synchronized void sortingOrderEntry(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
        param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
    }

    /**
     * 空箱入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-07 21:22:41
     */
    @Override
    public synchronized void emptyEntry(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_ELEVEN);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_THREE);
        upOrDownPolicy.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
        param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
//        param.getGoodsBox().setWeight(BigDecimal.ZERO);
        this.entry(param, upOrDownPolicy);
        //合箱，前面几个箱子原料箱变空箱，调整库位
        if (param.isDoEmpty()) {
            GoodsBox goodsBoxInfo = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
            warehousingEntryService.boxShelfLocationMsgSingle(goodsBoxInfo, true);
        }
    }

    /**
     * 合箱空箱入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-07 21:22:41
     */
    @Override
    public synchronized void emptyEntryOfMerge(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }

        List<Object> objectList = redisCache.getList(CacheConstants.MERGE_BOX_DETAIL_LIST + upOrDownPolicy.getClientId(), 0, -1);
        if (objectList != null) {
            List<Object> objects = new ArrayList<>();
            for (Object object : objectList) {
                GoodsBoxDetail goodsBoxDetail = JSONUtil.toBean(JSONUtil.toJsonStr(object), GoodsBoxDetail.class);
                if (goodsBoxDetail.getBoxCode().equals(upOrDownPolicy.getBoxCode())) {
                    goodsBoxDetail.setBoxStatus(Constants.GOODS_BOX_TYPE_ZERO);
                }
                objects.add(goodsBoxDetail);
            }
            redisCache.del(CacheConstants.MERGE_BOX_DETAIL_LIST + upOrDownPolicy.getClientId());
            if (!objects.isEmpty()) {
                redisCache.setAllListOfLeft(CacheConstants.MERGE_BOX_DETAIL_LIST + upOrDownPolicy.getClientId(), objects);
            }
        }

//        emptyEntry(param, upOrDownPolicy);
    }

    /**
     * 采购入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-07 21:22:27
     */
    @Override
    public synchronized void productEntry(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
        param.getUpOrDownPolicy().setDeliveryOrderCode(param.getDeliveryOrderCode());
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_ELEVEN);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_THREE);
        param.setOutboundAndEntry(true);
        param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ONE);
        param.getGoodsBox().setStatus(Constants.GOODS_BOX_STATE_ZERO);
        GoodsBox goodsBox;
        //大件商品
        if (param.getBigProduct() != null && param.getBigProduct().equals(com.lvy.hczn.front.common.constant.Constants.GOODS_COLLECT_YES)) {
            goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
        } else {
            goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()).in(GoodsBox::getStatus, Constants.GOODS_BOX_STATE_TWO, Constants.GOODS_BOX_STATE_FIVE, Constants.GOODS_BOX_STATE_SIX));
        }
        if (goodsBox != null) {
            //区分采购入库还是调度入库，调度入库，不更改入库时间
            if (goodsBox.getWarehousingEntryTime() == null) {
                param.getGoodsBox().setWarehousingEntryTime(LocalDateTime.now());
            }
//            goodsBoxDetailService.delPhysicalByBoxCode(goodsBox.getBoxCode());
            //todo 采购入库，仓与仓之间的调度时，货箱数据已同步存在，只需发送入库指令即可
            //采购入库方法考虑todo处理
            //货箱入库离开库位响应再保存货箱信息
            /*离开缓存位时，更新货箱详情ID，出库且不入库的时候会删除详情，根据此条件过滤再次发送指令（如果有的话）**/
            String boxKey = CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getProcessType();
//            Param paramInfo = redisCache.getCacheObject(boxKey);
            boolean exist = false;
            /*if (paramInfo != null) {
                if (paramInfo.getGoodsBox() != null) {
                    if (paramInfo.getList() != null && !paramInfo.getList().isEmpty()) {
                        for (GoodsBoxDetail detail : paramInfo.getList()) {
                            if (!StrUtil.isEmpty(detail.getId())) {
                                exist = true;
                                break;
                            }
                        }
                    }
                }
            }*/
            if (!exist) {
                this.entry(param, upOrDownPolicy);
                //忘了，应该直接用node-red模拟WCS响应的，后期再看要不要调整
                if (param.getBigProduct() != null && param.getBigProduct().equals(com.lvy.hczn.front.common.constant.Constants.GOODS_COLLECT_YES)) {
                    productEntryGoodsCollect(param, upOrDownPolicy);
                    redisCache.del(boxKey);
                }
            }
        } else {
            throw new UtilException("货箱信息不存在");
        }
    }

    /**
     * @Description 大件商品采购入库处理
     * @Param
     * @Author yw
     * @Date 2024/6/22 16:01
     * @Return
     **/
    public void productEntryGoodsCollect(Param param, UpOrDownPolicy upOrDownPolicy) {
        goodsBoxService.getGoodsBoxByKey(upOrDownPolicy);
        upOrDownPolicy.setDeliveryOrderCode(param.getDeliveryOrderCode());
        deliveryOrderService.updateByEntry(upOrDownPolicy);
    }

    /**
     * 强制入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-07 21:22:11
     */
    @Override
    public synchronized void entryForceInfo(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(param.getUpOrDownPolicy().getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
        if (!StrUtil.isEmpty(param.getDeliveryOrderCode())) {
            //采购入库时
            param.getUpOrDownPolicy().setDeliveryOrderCode(param.getDeliveryOrderCode());
        }

        /**若是原料箱，则带原料强制入库**/
        List<GoodsBoxDetail> detailList = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, upOrDownPolicy.getBoxCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
        if (!detailList.isEmpty()) {
            /**合箱强制入库的时候，把其他货箱的物品，放到合箱的货箱内**/
            if (param.getList() != null && !param.getList().isEmpty()) {
                List<GoodsBoxDetail> goodsBoxDetailList = new ArrayList<>();
                LinkedHashMap<String, List<GoodsBoxDetail>> goodsDetailMap = param.getList().stream().collect(Collectors.groupingBy(GoodsBoxDetail::getId, LinkedHashMap::new, Collectors.toList()));
                for (GoodsBoxDetail goodsBoxDetail : detailList) {
                    if (!goodsDetailMap.containsKey(goodsBoxDetail.getId())) {
                        goodsBoxDetailList.add(goodsBoxDetail);
                    }
                }
                if (!goodsBoxDetailList.isEmpty()) {
                    detailList.addAll(goodsBoxDetailList);
                }
            }

            param.setList(detailList);
        }
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_ELEVEN);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_FOUR);
        //强制入库，材料入库时也会存在强制入库，需要传false，后面再改
        //todo 强制入库，材料入库时也会存在强制入库，需要传false，后面再改采购入库传
//        param.setOutboundAndEntry(true);
        this.entry(param, upOrDownPolicy);
    }

    /**
     * 货箱净重称重
     *
     * @param param
     * @return com.lvy.hczn.common.core.util.ResultUtil
     * @author yw
     * @date 2022-04-07 20:20:51
     */
    @Override
    public synchronized void netWeightInit(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
        //毛重参数：bit9+bit5+boxCode+seatID
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_NINETEEN);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_FIVE);
        upOrDownPolicy.setType(Constants.BOX_OPERATION_WEIGHT);
        param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
        param.getGoodsBox().setStatus(Constants.GOODS_BOX_STATE_FIVE);
        if (param.getMap() == null) {
            param.setMap(new WcsData());
        }
        param.getMap().setBoxCode(param.getUpOrDownPolicy().getBoxCode());
//        param.getMap().setTakeNum(IdUtil.getSnowflake().nextIdStr());
        this.entry(param, param.getUpOrDownPolicy());
    }

    /**
     * 出库且不入库，更根据属性outbound区分货箱类型
     *
     * @param param
     * @return com.lvy.hczn.common.core.util.ResultUtil
     * @author yw
     * @date 2022-04-07 20:20:51
     */
    @Override
    public synchronized void notEntry(Param param, Integer outboundType, Integer type) throws Exception {
        //批量下架空箱，则不校验货箱编号
        if (StrUtil.isEmpty(param.getGoodsBox().getBoxCode()) && Constants.OUTBOUND_TYPE_ONE != outboundType) {
            throw new UtilException("货箱编号为空");
        }
        param.getUpOrDownPolicy().setProcessState(Constants.STATE_TO_WCS_SEVEN);
        param.getUpOrDownPolicy().setProcessType(Constants.STATE_PROCESS_TYPE_ZERO);
        param.getUpOrDownPolicy().setType(Constants.BOX_OPERATION_DOWN);
        param.setOutbound(outboundType);

        if (!StrUtil.isEmpty(param.getUpOrDownPolicy().getBoxCode())) {
            GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, param.getUpOrDownPolicy().getBoxCode()));
            if (goodsBox == null) {
                throw new UtilException("货箱不存在");
            }
            //指定货箱出库时，过滤非空箱商品
            if (param.getOperationPage() == 1 && !goodsBox.getBoxType().equals(Constants.GOODS_BOX_TYPE_ZERO)) {
                throw new UtilException("该货箱不能指定出库");
            }
        }

        //区分订单出库还是空箱或者指定货箱编号出库，订单出库则提前生成出库单
        if (param.getOutboundNumber() != null) {
            /**出库暂时以出库单方式出库，不论是指定货箱编号，还是指定数量批量出库**/
            if (param.getMap() == null) {
                param.setMap(new WcsData());
            }
            if (param.getOutboundNumber() != null) {
                param.getMap().setTotalWeight(BigDecimal.valueOf(param.getOutboundNumber()));
            }
            if (!StrUtil.isEmpty(param.getGoodsBoxSpecType()) && NumberUtil.isInteger(param.getGoodsBoxSpecType())) {
                //空箱批量出库时，指定大小货箱传到wcs，暂时用BoxCode代替货箱类型
                param.getMap().setBoxCode(param.getGoodsBoxSpecType());
                //批量出库，用boxCode发送到wcs代表出库大货箱或者小货箱
                param.getUpOrDownPolicy().setBoxCode(param.getGoodsBoxSpecType());
            }
            //取货单，响应的时候，web页面可以数量-1
            String outBound = IdUtil.getSnowflake().nextIdStr();
            param.getMap().setTakeNum(outBound);
            param.getUpOrDownPolicy().setTakeNum(outBound);
            param.getUpOrDownPolicy().setOutboundOrderCode(outBound);
            initOutboundOrder(param, param.getUpOrDownPolicy(), outBound, type);
        }

        /*if (!param.isOutboundAndEntry() && !StrUtil.isEmpty(param.getUpOrDownPolicy().getBoxCode())) {
            goodsBoxDetailService.delPhysicalByBoxCode(param.getUpOrDownPolicy().getBoxCode());
        }*/
        this.sendMessage(param, param.getUpOrDownPolicy());

        /*if (param.getOutboundNumber() != null && MqConstants.OUTBOUND_TYPE_ONE == outboundType) {
         *//**空箱批量出库**//*
            param.getMap().setTotalWeight(BigDecimal.valueOf(param.getOutboundNumber()));
            //取货单，响应的时候，web页面可以数量-1
            String outBound = IdUtil.getSnowflake().nextIdStr();
            param.getMap().setTakeNum(outBound);
            param.getUpOrDownPolicy().setOutboundOrderCode(outBound);
            initOutboundOrder(param, param.getUpOrDownPolicy(), outBound);
            this.sendMessage(param, param.getUpOrDownPolicy());
        } else {
            this.sendMessage(param, param.getUpOrDownPolicy());
        }*/
    }

    /**
     * 采购入库根据温区空箱出库，每个温区出库一个，然后离开缓存位后校验此货箱温区是否还有没入库的，有则再出库空箱
     *
     * @param param
     * @param outboundType
     * @param type
     * @return void
     * @author yw
     * @date 2022-09-05 17:02:53
     */
    @Override
    public synchronized void purchaseEmptyDown(Param param, Integer outboundType, Integer type) throws Exception {
        String key = CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + param.getUpOrDownPolicy().getClientId() + ":deliverOrderCode:done";
        Object object = redisCache.get(key);
        if (object == null || param.getTempNum() != null) {
            List<PurchaseOrderDetail> list = deliveryOrderDetailService.entryByDeliverCode(param);
            if (!list.isEmpty()) {
                Map<String, List<PurchaseOrderDetail>> mapByTemperatureZoneCode = list.stream().collect(Collectors.groupingBy(item -> item.getTemperatureZoneCode()));
                for (Map.Entry<String, List<PurchaseOrderDetail>> purchase : mapByTemperatureZoneCode.entrySet()) {
                    param.setOutboundAndEntry(true);
                    param.setTempNum(Integer.valueOf(purchase.getValue().get(0).getTemperatureZoneCode()));
                    //出库一个
                    param.setOutboundNumber(1);
                    param.getUpOrDownPolicy().setTotalWeight(BigDecimal.ONE);
                    param.getUpOrDownPolicy().setId(IdUtil.getSnowflake().nextIdStr());
                    //查询仓库内温区
//                    GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getStatus, MqConstants.GOODS_BOX_STATE_THREE).eq(GoodsBox::getBoxType, MqConstants.GOODS_BOX_TYPE_ZERO).eq(GoodsBox::getActTemperatureCode, purchase.getKey()).last("limit 1"));
                    //第一次，则全温区发，wcs同一topic按时读取，先强制处理
                    boolean flag;
                    int num = 0;
                    do {
                        try {
                            Boolean result = (Boolean) redisCache.get(CacheConstants.WCS_RESPONSE + "wms");
                            if (result != null && result == false) {
                                log.error("wcs忙碌中...");
                                Thread.sleep(500);
                                flag = true;
                            } else {
                                param.getUpOrDownPolicy().setBoxCode(null);
                                log.error("purchaseEmptyDown,{}", JSONUtil.toJsonStr(param));
                                notEntry(param, outboundType, type);
                                flag = false;
                            }
                        } catch (Exception e) {
                            log.error("purchaseEmptyDown:{}", e);
                            flag = true;
                            num++;
                            Thread.sleep(50);
                            if (num >= 100) {
                                flag = false;
                            }
                        }
                    } while (flag);
                }
                redisCache.set(key, true, 3, TimeUnit.DAYS);
            }
        }
    }

    /**
     * 生成出库单
     * 空出指定数量出库时，跳转到以出库单方式出库
     *
     * @param param
     * @param upOrDownPolicy
     * @param outbound
     * @return void
     * @author yw
     * @date 2022-06-09 17:31:58
     */
    public void initOutboundOrder(Param param, UpOrDownPolicy upOrDownPolicy, String outbound, Integer type) {
        Warehouse warehouse = warehouseService.selectLocalWarehouseFront();
        OutboundOrder outboundOrder = new OutboundOrder();
        outboundOrder.setOutboundOrderCode(outbound);
        outboundOrder.setWarehouseCode(warehouse.getWarehouseCode());
        //todo 带箱出库，理论上前置仓不带箱出库，到时确认
        outboundOrder.setOutWithBox(0);
        outboundOrder.setOutboundOrderType(type);
        outboundOrder.setOutboundTime(LocalDateTime.now());
        outboundOrderService.save(outboundOrder);

        //指定空箱数量出库时，totalWeight为要出库的货箱数量
        BigDecimal num = upOrDownPolicy.getTotalWeight();
        if (num != null && num.compareTo(BigDecimal.ZERO) > 0) {
            int number = num.intValue();
            for (int i = 0; i < number; i++) {
                OutboundOrderDetail outboundOrderDetail = new OutboundOrderDetail();
                outboundOrderDetail.setWarehouseCode(warehouse.getWarehouseCode());
                outboundOrderDetail.setOutboundOrderCode(outboundOrder.getOutboundOrderCode());
                outboundOrderDetail.setStatus(Constants.OUTBOUND_DETAIL_STATUS_ZERO);
                outboundOrderDetailService.save(outboundOrderDetail);
            }
        }

    }

    /**
     * 分拣中称重
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-05-29 10:15:09
     */
    @Override
    public synchronized ResultUtil sortingWeightInfo(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            throw new UtilException("货箱编号为空");
        }
        if (upOrDownPolicy.getTotalWeight() == null || upOrDownPolicy.getTotalWeight().compareTo(BigDecimal.ZERO) < 0) {
            throw new UtilException("重量不符");
        }
        upOrDownPolicy.setDataState(0);
        upOrDownPolicy.setStatus(Constants.UP_OR_DOWN_POLICE_STATUS_ZERO);
        upOrDownPolicy.setType(Constants.BOX_OPERATION_WEIGHT);
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_SEVENTEEN);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_TWO);
        GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()).eq(GoodsBox::getStatus, Constants.GOODS_BOX_STATE_TWO));
        if (goodsBox != null) {
            this.sendMessage(param, upOrDownPolicy);
            return ResultUtil.ok();
        } else {
            ResultUtil resultUtil = new ResultUtil();
            resultUtil.setCode(Constants.WMS_SORT_WEIGHT_ERROR);
            return resultUtil;
        }
    }

    /**
     * 采购称重
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-05-29 10:15:09
     */
    @Override
    public synchronized void entryWeight(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            throw new UtilException("采购称重货箱编号为空");
        }
        if (upOrDownPolicy.getTotalWeight() == null || upOrDownPolicy.getTotalWeight().compareTo(BigDecimal.ZERO) < 0) {
            throw new UtilException("采购称重重量不符");
        }
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_SEVENTEEN);
        upOrDownPolicy.setStatus(Constants.UP_OR_DOWN_POLICE_STATUS_ZERO);
        upOrDownPolicy.setType(Constants.BOX_OPERATION_WEIGHT);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_SEVEN);
        param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
        if (param.getMap() == null) {
            param.setMap(new WcsData());
        }
        param.getMap().setTotalWeight(BigDecimal.ZERO);
        param.getMap().setBoxCode(param.getUpOrDownPolicy().getBoxCode());
        this.sendMessage(param, upOrDownPolicy);
    }

    /**
     * wcs返回状态到前端或者浏览器重新打开进入对应页面，则调此方法获取wcs返回信息
     *
     * @param type
     * @param clientId
     * @return com.lvy.hczn.common.core.util.ResultUtil
     * @author yw
     * @date 2022-05-20 17:22:35
     */
    @Override
    public MessageInfo selectResponseFromWcs(String type, String clientId, String processType) throws Exception {
        if (!StrUtil.isAllNotEmpty(type, clientId, processType)) {
            throw new UtilException("参数为空");
        }
        /**不直接弹出，由用户选择是否删除，防止误操作**/
        MessageInfo messageInfo = null;
        String key = type + Constants.TYPE_WCS_RESPONSE + clientId;
        List<Object> list = redisCache.getList(key, 0, -1);
        log.error("web获取货箱：{}，{}", type, JSON.toJSONString(list));
        if (list != null && !list.isEmpty()) {
            messageInfo = JSONUtil.toBean(JSONUtil.toJsonStr(list.get(0)), MessageInfo.class);
        }
        return messageInfo;
    }

    /**
     * 销售退货入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2023-09-14 16:58:13
     */
    @Override
    public synchronized void returnEntry(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(param.getUpOrDownPolicy().getBoxCode())) {
            throw new UtilException("货箱编码为空");
        }
        /*if (param.getDetailList() == null || param.getDetailList().isEmpty()) {
            throw new UtilException("退货详情为空");
        }*/

        /**销售退货单转化为货箱信息**/
        GoodsBox goodsBox = goodsBoxService.selectIncDetailByBoxCode(upOrDownPolicy.getBoxCode());
        if (goodsBox == null) {
            throw new UtilException("仓库不存在该货箱编号信息");
        }
        List<GoodsBoxDetail> boxDetails = new ArrayList<>();
        if (param.getDetailList() != null && !param.getDetailList().isEmpty()) {
            //todo 退货配送,俩拒收方式，后期可能需要过滤
            /*SaleOrder saleOrder = saleOrderService.getOne(Wrappers.<SaleOrder>lambdaQuery().eq(SaleOrder::getSaleOrderCode, param.getDetailList().get(0).getSaleOrderCode()).in(SaleOrder::getType, 2, 4).last("limit 1"));
            if (saleOrder == null) {
                throw new UtilException("销售退货单不存在");
            }*/
            param.getDetailList().forEach(saleOrderDetail -> {
                SaleOrderDetail detail = saleOrderDetailService.getById(saleOrderDetail.getId());
                SaleOrder saleOrder = saleOrderService.getOne(Wrappers.<SaleOrder>lambdaQuery().eq(SaleOrder::getSaleOrderCode, saleOrderDetail.getSaleOrderCode()).in(SaleOrder::getType, 2, 4).last("limit 1"));
                if (detail != null && saleOrder != null) {
                    GoodsBoxDetail boxDetail = new GoodsBoxDetail();
                    Spu spu = spuService.getById(detail.getSpuId());
                    boxDetail.setId(IdWorker.getIdStr());
                    boxDetail.setBoxCode(goodsBox.getBoxCode());
                    boxDetail.setEntryCode(saleOrderDetail.getSaleOrderCode());
                    boxDetail.setWeight(saleOrderDetail.getWeight());
                    boxDetail.setPurchaseWeight(saleOrderDetail.getWeight());
                    boxDetail.setGoodsCode(spu.getSpuCode());
                    boxDetail.setSpuId(saleOrderDetail.getSpuId());
                    boxDetail.setSkuCode(saleOrderDetail.getSkuCode());
                    boxDetail.setSpecCode(saleOrderDetail.getSpecCode());
                    boxDetail.setPurchaseOrderCode(saleOrderDetail.getSaleOrderCode());
                    boxDetail.setQuantity(saleOrderDetail.getQuantity());
                    boxDetail.setUnit(saleOrderDetail.getUnit());
                    boxDetail.setNumberOfPerBox(boxDetail.getQuantity());
                    try {
                        boxDetail.setWeightOfPerBox(saleOrderDetail.getWeight().divide(BigDecimal.valueOf(saleOrderDetail.getQuantity()), 2, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP));
                    } catch (Exception e) {
                        log.error("退货入库设置每份重量异常：{}", JSON.toJSONString(saleOrderDetail));
                    }

//                    boxDetail.setNumberOfPerBox(0);
//                    boxDetail.setWeightOfPerBox(BigDecimal.ZERO);
                    if (saleOrderDetail.getVolume() == null) {
                        boxDetail.setVolume(BigDecimal.ZERO);
                    } else {
                        boxDetail.setVolume(saleOrderDetail.getVolume());
                    }
                    boxDetail.setInBulk(false);
                    boxDetail.setInMerge(false);
                    boxDetail.setInReturn(true);
                    boxDetail.setSaleOrderCode(detail.getSaleOrderCode());
                    //todo 分拣退货后，这里取的还不够准确，平台端退货需传入生产日期，不然退货入库不知道哪个生产日期，影响到入库推送
                    //场景：商城同一人买多份，从不同批次货箱拿，退货时，商城现在无法区分，即：仓库有不同批次，且用户购买，一个批次满足不了，同时，用户退货的情况下会出现生产日期不同
                    WarehousingEntry entry = warehousingEntryService.selectBySaleOrder(com.lvy.hczn.front.common.constant.Constants.WAREHOUSING_ENTRY_DATA_TYPE_EIGHT, saleOrder, detail);

                    //暂时取最新的
                    if (entry != null) {
                        boxDetail.setProducerId(entry.getProducerId());
                        boxDetail.setSupplierCode(entry.getSupplierCode());
                        boxDetail.setPurchaseOrderCode(entry.getEntryCode());
                        boxDetail.setBatchCode(entry.getBatchCode());
                        boxDetail.setWarrantyDate(entry.getWarrantyDate());
                        boxDetail.setProduceDate(entry.getPickTime());
                        if (entry.getWeightOfPerBox() != null && entry.getWeightOfPerBox().compareTo(BigDecimal.ZERO) > 0) {
                            boxDetail.setWeightOfPerBox(entry.getWeightOfPerBox());
                        }
                        if (entry.getPickTime() != null && entry.getWarrantyDate() != null) {
                            boxDetail.setShelfLife(entry.getPickTime().toLocalDate().plusDays(entry.getWarrantyDate()));
                        }
                    }
                    boxDetails.add(boxDetail);
                    goodsBox.getDetailList().add(boxDetail);
                }
            });
        }
        if (!boxDetails.isEmpty()) {
            redisCache.set(CacheConstants.CACHE_RETURN + goodsBox.getBoxCode() + ":" + upOrDownPolicy.getClientId(), JSON.toJSONString(boxDetails), 3, TimeUnit.DAYS);
        }

        log.error("销售退货货箱详情：{}", JSON.toJSONString(goodsBox));

        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_NINE);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_THREE);
        upOrDownPolicy.setType(Constants.BOX_OPERATION_UP);
        param.setGoodsBox(goodsBox);
        param.setList(goodsBox.getDetailList());
//            param.getGoodsBox().setBoxType(Constants.GOODS_BOX_TYPE_ONE);
        //退货入库，强制入库
        if (param.isReturnEntry()) {
            entryForceInfo(param, param.getUpOrDownPolicy());
        } else {
            this.entry(param, upOrDownPolicy);
        }

        if (param.getBigProduct() != null && param.getBigProduct().equals(com.lvy.hczn.front.common.constant.Constants.GOODS_COLLECT_YES)) {
            returnEntryGoodsCollect(param, upOrDownPolicy);
        }
    }

    /**
     * @Description 大件商品退货入库
     * @Param
     * @Author yw
     * @Date 2024/6/22 15:17
     * @Return
     **/
    public void returnEntryGoodsCollect(Param param, UpOrDownPolicy upOrDownPolicy) {
        SearchCondition searchCondition = new SearchCondition();
        searchCondition.setBoxCode(param.getUpOrDownPolicy().getBoxCode());
        searchCondition.setClientId(upOrDownPolicy.getClientId());
        goodsBoxService.getGoodsBoxByKey(upOrDownPolicy);
        saleOrderService.returnCallBack(searchCondition);
    }

    /**
     * 临期报损入库
     *
     * @param param
     * @param upOrDownPolicy
     * @return
     * @author yw
     * @date 2022-04-19 22:48:49
     */
    @Override
    public synchronized void frmLossEntry(Param param, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (StrUtil.isEmpty(param.getUpOrDownPolicy().getBoxCode())) {
            throw new UtilException("临期报损入库货箱编码为空");
        }

        //入库时，获取未临期报损的货箱详情ID列表，离开缓存位时，清空货箱内不在此列表的货箱详情
//        List<String> boxDetailIdList = param.getList().stream().map(BaseModel::getId).collect(Collectors.toList());
//        redisCache.setListOfRight(CacheConstants.CACHE_FRM_LOSS + upOrDownPolicy.getBoxCode(), boxDetailIdList);

        GoodsBox goodsBox = goodsBoxService.selectIncDetailByBoxCode(param.getUpOrDownPolicy().getBoxCode());
        upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_NINE);
        upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_THREE);
        upOrDownPolicy.setType(Constants.BOX_OPERATION_UP);
        if (param.getList() == null || param.getList().isEmpty()) {
            goodsBox.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
            log.error("frmLossEntry临期报损变空箱：{}", JSON.toJSONString(goodsBox));
            upOrDownPolicy.setBoxType(goodsBox.getBoxType());
            //原料箱变空箱后，库位调整，释放库位
//            warehousingEntryService.boxShelfLocationMsgSingle(goodsBox, true);
        }
        param.setGoodsBox(goodsBox);
        param.setList(param.getList());
        this.entry(param, upOrDownPolicy);
    }

    /**
     * wcs叠箱百分比定时任务执行
     *
     * @param
     * @return void
     * @author yw
     * @date 2023-10-21 20:20:29
     */
    @Override
    public void stackBoxesPercent(String percent) throws Exception {
        if (StrUtil.isEmpty(percent)) {
            percent = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.SCHEDULED_TASKS, com.lvy.hczn.front.common.constant.Constants.SCHEDULED_TASKS_WAREHOUSE_PERCENT);
        }
        if (!StrUtil.isEmpty(percent) && NumberUtil.isInteger(percent)) {
            Integer num = Integer.parseInt(percent);
            if (0 <= num && num <= 100) {
                WcsData wcsData = new WcsData();
                wcsData.setState(num);
                sendDelay();
                String jsonStr = JSON.toJSONString(wcsData);
                log.error("叠箱百分比发送到wcs数据内容：{}", jsonStr);
                MqttRecord mqttRecord = new MqttRecord();
                mqttRecord.setBoxCode("0");
                mqttRecord.setState(num);
                mqttRecord.setDone(false);
                mqttRecord.setTopic(Constants.TOPIC_MQTT_STACK_BOXES_PERCENT);
                mqttRecord.setContent(jsonStr);
                if (mqttRecordService.save(mqttRecord)) {
                    redisCache.set(CacheConstants.WCS_RESPONSE + ":percent", JSON.toJSONString(mqttRecord));
                    mqttService.sendToMqtt(Constants.TOPIC_MQTT_STACK_BOXES_PERCENT, jsonStr);
                }
            } else {
                throw new UtilException("参数只能在0-100之间:" + percent);
            }
        } else {
            throw new UtilException("参数为非整数:" + percent);
        }
    }

    /**
     * 箱子放到缓存位自动扫描入库，根据进入的页面进行判断入库的货箱类型
     * <p>
     * 1、WMS操作人员进入对应的操作页面
     * 2、redis缓存页面操作类型以及仓库编号
     * 3、WCS反馈仓库和货箱编号
     * 4、跟WCS的反馈，从Redis查询对应的页面操作
     * 5、返回页面获取数据执行指令
     *
     * @param response
     * @return
     * @author yw
     * @date 2022-04-19 22:48:49
     */
    @Override
    public synchronized void entryByPageType(WcsResponse response) {
        Integer warehousingNum = entryWcsCheck(response);
        if (warehousingNum != null) {
            Map<String, Object> resultMap;
            JSONArray array = (JSONArray) redisCache.get(CacheConstants.DICT_TYPE_WH_COM);
            if (array != null && !array.isEmpty()) {
                /**获取上一个下架货箱的仓库index**/
                for (int i = 0; i < array.size(); i++) {
                    SysDictData data = (SysDictData) array.get(i);
                    String key = CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + data.getDictValue() + ":deliverOrderCode";
                    String deliverOrderCode = redisCache.getCacheObject(key);
                    if (!StrUtil.isEmpty(deliverOrderCode)) {
                        warehousingNum = Integer.valueOf(data.getDictValue());
                        break;
                    }
                }
            }

            Map<String, Object> map = redisCache.getCacheMap(RedisConstants.OPERATOR_PAGE_TYPE + warehousingNum);

            Object uuid = redisCache.get(CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + warehousingNum);
            if (uuid != null) {
                UUID id = UUID.fromString(String.valueOf(uuid));
                SocketIOClient socketIOClient = socketIOServer.getClient(id);
                if (socketIOClient != null) {
                    if (map != null) {
                        //登录页面时，初始化的pageType在/mqtt/index/test函数
                        String pageType = String.valueOf(map.get("pageType"));
                        //pageType做扩展，暂时未用到
                        if (map.containsKey("pageType") && map.containsKey("pageWarehousing")) {
                            List<SysDictData> list = DictUtils.getDictCache(com.lvy.hczn.front.common.constant.Constants.SYS_DICT_ENTRY_PAGE_TYPE);
                            if (list == null || list.isEmpty()) {
                                resultMap = AjaxResult.error("数据字典未配置页面数据类型", map);
                            } else {
                                boolean flag = false;
                                for (SysDictData sysDictData : list) {
                                    if (sysDictData.getDictValue().equals(pageType)) {
                                        Map<String, Object> beanMap = BeanUtil.beanToMap(response);
                                        map.putAll(beanMap);
                                        flag = true;
                                        break;
                                    }
                                }
                                if (flag) {
                                    resultMap = AjaxResult.success(map);
                                } else {
                                    resultMap = AjaxResult.error("数据字典数据字典不存在该页面数据类型", map);
                                }
                            }
                        } else {
                            //返回参数缺失提示到页面
                            resultMap = AjaxResult.error("pageType类型或者仓库为空", map);
                        }
                    } else {
                        //返回异常提示到页面
                        resultMap = AjaxResult.error("页面参数信息为空");
                    }
                    socketIOClient.sendEvent("pageEntry", JSON.toJSONString(resultMap));
                } else {
                    log.error("socketIo获取客户端信息为空：｛｝", JSON.toJSONString(map));
                }
            }
        }
    }

    /**
     * @Description 延时发送到WCS，说是需要延时一下
     * @Param
     * @Author yw
     * @Date 2024/6/5 15:08
     * @Return
     **/
    @Override
    public void sendDelay() throws Exception {
        String delay = redisCache.getCacheObject(RedisConstants.WCS_SEND_DELAY);
        if (!StrUtil.isEmpty(delay)) {
            Thread.sleep(Long.parseLong(delay));
        } else {
            Thread.sleep(Constants.TOPIC_MQTT_SEND_DELAY);
            delay = String.valueOf(Constants.TOPIC_MQTT_SEND_DELAY);
        }
        log.error("延时：{}", delay);
    }

    /**
     * @Description WCS反馈的数据校验
     * @Param [response, warehousingNum]
     * @Author yw
     * @Date 2024/5/24 10:31
     * @Return void
     **/
    public Integer entryWcsCheck(WcsResponse response) {
        Integer warehousingNum = null;
        if (response != null) {
            if (!StrUtil.isEmpty(response.getBoxCode())) {
                if (response.getSeatID() == null || response.getSeatID().isEmpty()) {
                    log.error("entryByPageType仓库返回仓库编号数据为空");
                } else {
                    for (int i = 0; i < response.getSeatID().size(); i++) {
                        Integer value = response.getSeatID().get(i);
                        if (value != null && value > 0) {
                            warehousingNum = value;
                            break;
                        }
                    }
                }
            } else {
                log.error("entryByPageType仓库返回货箱编号为空");
            }
        } else {
            log.error("entryByPageType仓库返回数据为空");
        }
        return warehousingNum;
    }

    /**
     * 二次扫码入库
     * 扫描仓库号，货箱编号，确定入当前库，默认topic为MQ2_WMS_TO_WCS
     * 参数为WMS二次扫码,获得货架编号+货箱编码
     *
     * @param response
     * @param upOrDownPolicy
     * @return
     * @author yw
     * @date 2022-03-20 15:17:25
     */
    @Override
    public void stockInBySecondScan(WcsResponse response, UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (response.getSeatID() == null) {
            throw new UtilException("仓库号为空");
        }
        if (StrUtil.isEmpty(response.getBoxCode())) {
            throw new UtilException("货箱编号为空");
        }
        Boolean signal = (Boolean) redisCache.get(CacheConstants.REDIS_KEY_WCS_SIGNAL);
        if (signal == null) {
            throw new UtilException("已与wcs断开连接");
        }
        boolean state = Constants.checkProcessState(upOrDownPolicy.getProcessState());
        if (state) {
            //客户取货入库||不入库/请求分拣入库||不入库
            if (upOrDownPolicy.getProcessState().equals(Constants.STATE_TO_WCS_TEN)) {
                //wcs要求结构体完整性，发0补全
                response.setAnswerID(0);
                response.setErrorID(0);
                response.setTotalWeight(BigDecimal.ZERO);
                log.info("二次扫码：" + JSONUtil.toJsonStr(response));
                mqttService.sendToMqtt(Constants.TOPIC_MQTT_MQ2_WMS_TO_WCS, JSONUtil.toJsonStr(response));
                upOrDownPolicy.setType(Constants.BOX_OPERATION_UP);
                upOrDownPolicyService.save(upOrDownPolicy);
            } else {
                throw new UtilException("非二次扫码流程");
            }
        } else {
            throw new UtilException("扫码流程不存在");
        }
    }

    public static void main(String[] args) {
        //2的多少次方为256
        System.out.println(Integer.valueOf((int) (Math.log(264) / Math.log(2))));
    }
}
